package com.conversationboard.tree;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import com.conversationboard.logger.Logger;
import com.conversationboard.model.Board;
import com.conversationboard.model.Boards;
import com.conversationboard.model.User;

public class Node {

	private int nodeId;
	private int boardId;
	private String folderName;
	private String folderDescription;
	private String linkHtml;
	private String linkDescription;
	private boolean root = false;
	private NodeType nodeType;
	private Node parentNode = null;
	private List<Node> childNodes = new ArrayList<Node>();


	public String getLinkDescription() {
		return linkDescription;
	}


	public void setLinkDescription(String linkDescription) {
		this.linkDescription = linkDescription;
	}


	public String getLinkHtml() {
		return linkHtml;
	}


	public void setLinkHtml(String linkHtml) {
		this.linkHtml = linkHtml;
	}


	public NodeType getNodeType() {
		return nodeType;
	}


	public void setNodeType(NodeType nodeType) {
		this.nodeType = nodeType;
	}


	public int getChildNodeCount() {
		return this.childNodes.size();
	}


	public void addChildNode(Node node) {
		this.childNodes.add(node);
	}


	/**
	 * Add a child node relative to the last node passed, using the position.
	 * 
	 * @param nodeToAdd
	 * @param position
	 * @param relativeTo
	 */

	public void addChildNode(User user, Node nodeToAdd, Position position, Node relativeTo) throws SQLException {

		List<Node> childNodes = this.getChildNodes(user);
		Node newNode = nodeToAdd.clone();

		if (position == Position.After) {

			for (int i = 0; i < childNodes.size(); i++) {

				Node node = childNodes.get(i);

				if (node.getNodeId() == relativeTo.getNodeId()) {

					ArrayList<Node> arrayList = (ArrayList<Node>) this.childNodes;
					arrayList.ensureCapacity(i + 1);

					childNodes.add(i + 1, newNode);
					break;
				}
			}

		} else if (position == Position.Before) {

			for (int i = 0; i < childNodes.size(); i++) {

				Node node = childNodes.get(i);

				if (node.getNodeId() == relativeTo.getNodeId()) {

					childNodes.add(i, newNode);
					break;
				}
			}

		}

	}


	public void removeChildNode(Node nodeToRemove) {
		this.childNodes.remove(nodeToRemove);
	}


	public int getNodeId() {
		return nodeId;
	}


	public void setNodeId(int nodeId) {
		this.nodeId = nodeId;
	}


	public String getFolderDescription() {
		return folderDescription;
	}


	public void setFolderDescription(String folderDescription) {
		this.folderDescription = folderDescription;
	}


	int getMaxNodeId(int currentMax) throws SQLException {

		int localMax = currentMax;

		for (Node node : this.getChildNodes(User.getSystemUser())) {
			if (node.getNodeId() > localMax) {
				localMax = node.getNodeId();
			}

			localMax = node.getMaxNodeId(localMax);
		}

		return localMax;
	}


	public Node(Node parentNode) throws SQLException {

		this.parentNode = parentNode;
		this.setNodeType(NodeType.Board);

		if (parentNode == null) {
			this.nodeId = 0;
		}

		if (parentNode != null) {
			int nextNodeId = Folders.getInstance().getNextNodeId();
			this.nodeId = nextNodeId;
		}

	}


	public Node getParentNode() {
		return parentNode;
	}


	public void setParentNode(Node parentNode) {
		this.parentNode = parentNode;
	}


	public Node(NodeType nodeType, Node parentNode) throws SQLException {

		this.setNodeType(nodeType);

		if (parentNode != null) {
			int nextNodeId = Folders.getInstance().getNextNodeId();
			this.nodeId = nextNodeId;
			this.parentNode = parentNode;
		}

	}


	/**
	 * Recursive search through the folder hierarchy to find a given node.
	 * @param folderId
	 * @return
	 */

	public Node findFolder(int nodeId) {

		if (this.getNodeId() == nodeId && (this.isFolder())) {
			return this;
		}

		for (Node node : this.childNodes) {

			if (node.isFolder()) {
				if (node.getNodeId() == nodeId) {
					return node;
				}
			}

			Node childNode = node.findFolder(nodeId);

			if (childNode != null) {
				return childNode;
			}

		}

		return null;
	}


	public Node findNode(int nodeId) {

		if (this.getNodeId() == nodeId) {
			return this;
		}

		for (Node node : this.childNodes) {

			if (node.getNodeId() == nodeId) {
				return node;
			}

			Node childNode = node.findNode(nodeId);

			if (childNode != null) {
				return childNode;
			}

		}

		return null;

	}


	public Node findBoard(int boardId) {

		if (this.getBoardId() == boardId && (this.isBoard())) {
			return this;
		}

		for (Node node : this.childNodes) {

			if (node.isBoard()) {
				if (node.getBoardId() == boardId) {
					return node;
				}
			}

			Node childNode = node.findBoard(boardId);

			if (childNode != null) {
				return childNode;
			}

		}

		return null;

	}


	/**
	 * Get child folders and don't return the root node.
	 * 
	 * @param user
	 * @param recurse
	 * @return
	 * @throws SQLException
	 */

	public List<Node> getChildFolders(User user, boolean recurse) throws SQLException {

		List<Node> folders = new LinkedList<Node>();
		this.getNodes(user, folders, false, true, false, recurse);

		return folders;
	}


	public List<Node> getChildBoards(User user, boolean recurse) throws SQLException {

		List<Node> boards = new LinkedList<Node>();
		this.getNodes(user, boards, true, false, false, recurse);

		return boards;
	}


	public List<Node> getChildLinks(User user, boolean recurse) throws SQLException {

		List<Node> boards = new LinkedList<Node>();
		this.getNodes(user, boards, false, false, true, recurse);

		return boards;
	}


	public List<Node> getChildNodes(User user, boolean recurse) throws SQLException {

		List<Node> nodes = new LinkedList<Node>();
		this.getNodes(user, nodes, true, true, true, recurse);

		return nodes;
	}


	private void getNodes(User user, List<Node> nodeList, boolean includeBoards, boolean includeFolders, boolean includeExternalLinks, boolean recurse) throws SQLException {

		for (Node node : this.getChildNodes(user)) {

			if (node.isFolder() && recurse) {
				node.getNodes(user, nodeList, includeBoards, includeFolders, includeExternalLinks, recurse);
			}

			if ((node.isFolder() && includeFolders) || (node.isBoard() && includeBoards) || (node.isExternalLink() && includeExternalLinks)) {

				if (node.isBoard() && user.canRead(node.getBoardId())) {
					nodeList.add(node);
				} else if (node.isFolder() || node.isExternalLink()) {
					nodeList.add(node);
				}
			}
		}
	}


	/**
	 * Returns a text representation of this given subtree.
	 * 
	 * @return
	 */

	void drawTree(User user, int currentLevel, StringBuilder buffer, boolean addNodeIDs) throws SQLException {

		/* First display the boards under the current folder */

		for (Node childNode : this.childNodes) {

			if (childNode.isBoard() || childNode.isExternalLink()) {

				if (user.canRead(childNode.getBoardId())) {

					buffer.append("|");

					for (int i = 0; i < currentLevel; i++) {
						buffer.append("----");
					}

					buffer.append("|");

					buffer.append(" ");

					if (childNode.isBoard()) {
						String boardName = "<span class=\"green\">" + Boards.getBoard(childNode.getBoardId()).getName() + "</span>";

						if (addNodeIDs) {
							boardName += " <span class=\"amber\">(Node ID: " + childNode.getNodeId() + ")</span>\n";
						} else {
							boardName += "\n";
						}

						buffer.append(boardName);

					} else {

						String linkName = "<span class=\"green\"><strong>[External Link]</strong> " + childNode.getLinkDescription() + "</span>";

						if (addNodeIDs) {
							linkName += " <span class=\"amber\">(Node ID: " + childNode.getNodeId() + ")</span>\n";
						} else {
							linkName += "\n";
						}

						buffer.append(linkName);

					}

				}
			}
		}

		/* Then recurse on the folders */

		for (Node childNode : this.childNodes) {

			if (childNode.isFolder()) {

				buffer.append("|");

				for (int i = 0; i < currentLevel; i++) {
					buffer.append("----");
				}

				buffer.append("|");

				String folderName = "<span class=\"blue\">" + childNode.getFolderName() + "</span>";

				if (addNodeIDs) {
					folderName += " <span class=\"amber\">(Node ID: " + childNode.getNodeId() + ")</span>";
				}

				buffer.append(" " + folderName + "\n");

				int nextLevel = currentLevel + 1;
				childNode.drawTree(user, nextLevel, buffer, addNodeIDs);
			}
		}

	}


	public boolean isRoot() {
		return root;
	}


	public void setRoot(boolean isRoot) {
		this.root = isRoot;
	}


	/**
	 * Get the children of the current node.
	 * 
	 * @param user
	 * @return
	 * @throws SQLException
	 */

	private List<Node> getChildNodes(User user) throws SQLException {

		List<Node> nodes = new LinkedList<Node>();

		for (Node node : this.childNodes) {

			if (node.isBoard() && user.canRead(node.getBoardId())) {
				nodes.add(node);
			}

			if (node.isFolder()) {
				nodes.add(node);
			}
		}

		return childNodes;
	}


	public int getBoardId() {
		return boardId;
	}


	public void setBoardId(int boardId) {
		this.boardId = boardId;
	}


	public Board getBoardObject() throws SQLException {

		if (this.isBoard()) {
			return Boards.getBoard(this.getBoardId());
		}

		return null;
	}


	public String getFolderName() {
		return folderName;
	}


	public void setFolderName(String folderName) {
		this.folderName = folderName;
	}


	public boolean isFolder() {
		return (this.nodeType == NodeType.Folder);
	}


	public boolean isBoard() {
		return (this.nodeType == NodeType.Board);
	}


	public boolean isExternalLink() {
		return (this.nodeType == NodeType.ExternalLink);
	}


	public void removeChildFolder(User user, int nodeId) throws SQLException {

		for (Node node : this.getChildNodes(user)) {
			if (node.isFolder() && node.getNodeId() == nodeId) {
				this.getChildNodes(user).remove(node);
			}
		}
	}


	public Node removeChildBoard(User user, int boardId, boolean recurse) throws SQLException {

		Node nodeToRemove = null;

		for (Node node : this.getChildNodes(user)) {

			if (node.isBoard() && node.getBoardId() == boardId) {
				nodeToRemove = node;
			}

			if (node.isFolder() && recurse) {
				node.removeChildBoard(user, boardId, recurse);
			}

		}

		if (nodeToRemove != null) {
			this.getChildNodes(user).remove(nodeToRemove);
		}

		return nodeToRemove;
	}


	public Node removeChildNode(User user, int nodeId, boolean recurse) throws SQLException {

		Node nodeToRemove = null;

		for (Node node : this.getChildNodes(user)) {

			if ((!node.isFolder()) && node.getNodeId() == nodeId) {
				nodeToRemove = node;
			}

			if (node.isFolder() && recurse) {
				node.removeChildNode(user, nodeId, recurse);
			}

		}

		if (nodeToRemove != null) {
			this.getChildNodes(user).remove(nodeToRemove);
		}

		return nodeToRemove;
	}


	/**
	 * Add a folder below this node.
	 * 
	 * @param folderName
	 */

	public void addFolder(User user, String folderName) throws SQLException {

		Node newFolder = new Node(NodeType.Folder, this);
		newFolder.setFolderName(folderName);

		this.getChildNodes(user).add(newFolder);
	}


	public void addBoard(User user, int boardId) throws SQLException {

		Node newBoard = new Node(NodeType.Board, this);
		newBoard.setBoardId(boardId);

		this.getChildNodes(user).add(newBoard);
	}


	public Node getChildNode(User user, int nodeId) throws SQLException {

		for (Node node : this.getChildNodes(user)) {

			if (node.getNodeId() == nodeId) {
				return node;
			}
		}

		return null;
	}


	public Node clone() {

		Node node = null;

		try {
			node = new Node(this.getParentNode());
			node.setBoardId(this.getBoardId());
			node.setFolderDescription(this.getFolderDescription());
			node.setFolderName(this.getFolderDescription());
			node.setLinkDescription(this.getLinkDescription());
			node.setLinkHtml(this.getLinkHtml());
			node.setNodeId(Folders.getInstance().getNextNodeId());
			node.setNodeType(this.getNodeType());
			node.setRoot(this.isRoot());

		} catch (SQLException e) {
			Logger.log(new Date(), "Unable to clone node", User.getSystemUser());
		}

		return node;
	}

}
